package com.tengfei.blogsystem.service.impl;

import com.google.gson.Gson;
import com.tengfei.blogsystem.dao.RefreshTokenDao;
import com.tengfei.blogsystem.dao.SettingsDao;
import com.tengfei.blogsystem.dao.UserDao;
import com.tengfei.blogsystem.pojo.RefreshToken;
import com.tengfei.blogsystem.pojo.Settings;
import com.tengfei.blogsystem.pojo.User;
import com.tengfei.blogsystem.response.ResponseResult;
import com.tengfei.blogsystem.response.ResponseState;
import com.tengfei.blogsystem.service.IUserService;
import com.tengfei.blogsystem.utils.*;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.Transactional;
import java.util.Date;
import java.util.Map;

/**
 * @version v1.0
 * @author} feige
 * @date 2021-10-16-16:04
 * @description
 */
@Service
@Transactional  // 事务管理
public class UserServiceImpl implements IUserService {

    @Autowired
    SnowFlakeIdWorker idWorker; // 雪花算法
    @Autowired
    UserDao userDao;
    @Autowired
    SettingsDao settingsDao;
    @Autowired
    BCryptPasswordEncoder cryptPasswordEncoder; // 密码加密
    @Autowired
    RedisUtil redisUtil;
    @Autowired
    RefreshTokenDao refreshTokenDao;
    @Autowired
    Gson gson;

    @Override
    public ResponseResult initManagerAccount(User user, HttpServletRequest request) {
        // 检查是否初始化
        Settings managerAccountState = settingsDao.findOneByKey(Constants.Settings.MANAGER_ACCOUNT_INIT_STATE);
        if (managerAccountState != null) {
            return new ResponseResult(ResponseState.ERROR, "账号已注册");
        }
        // 检查数据
        if (TextUtils.isEmpty(user.getUserName())) {
            return new ResponseResult(ResponseState.ERROR, "用户名为空");
        }
        if (TextUtils.isEmpty(user.getEmail())) {
            return new ResponseResult(ResponseState.ERROR, "邮箱为空");
        }
        if (TextUtils.isEmpty(user.getPassWord())) {
            return new ResponseResult(ResponseState.ERROR, "密码为空");
        }
        // 密码加密
        user.setPassWord(cryptPasswordEncoder.encode(user.getPassWord()));
        // 补充数据
        user.setId(String.valueOf(idWorker.nextId()));
        user.setRoles(Constants.User.ROLE_ADMIN);
        user.setAvatar(Constants.User.DEFAULT_AVATAR);
        user.setState(Constants.User.DEFAULT_STATE);
        user.setRegIp(request.getRemoteAddr());
        user.setLoginIp(request.getRemoteAddr());
        user.setCreateTime(new Date());
        user.setUpdateTime(new Date());
        // 保存到数据库
        userDao.save(user);
        // 更新标记
        Settings settings = new Settings();
        settings.setId(String.valueOf(idWorker.nextId()));
        settings.setCreateTime(new Date());
        settings.setUpdateTime(new Date());
        settings.setKey(Constants.Settings.MANAGER_ACCOUNT_INIT_STATE);
        settings.setValue("1");
        settingsDao.save(settings);
        return new ResponseResult(ResponseState.SUCCESS, "注册成功");
    }

    @Override
    public ResponseResult register(User user, String emailCode, String checkCode, String checkKey, HttpServletRequest request) {
        // 第一步：检查用户是否注册
        String userName = user.getUserName();
        if (TextUtils.isEmpty(userName)) {
            return new ResponseResult(ResponseState.ERROR, "用户名不能为空");
        }
        User userByUserName = userDao.findOneByuserName(userName);
        if (userByUserName != null) {
            return new ResponseResult(ResponseState.ERROR, "用户名已存在");
        }
        // 第二步：检查邮箱格式是否正确
        String email = user.getEmail();
        if (TextUtils.isEmpty(email)) {
            return new ResponseResult(ResponseState.ERROR, "邮箱不能为空");
        }
        if (!TextUtils.isEmailAddress(email)) {
            return new ResponseResult(ResponseState.ERROR, "请输入正确的邮箱地址");
        }
        // 第三步：检查邮箱是否注册
        User userByEmail = userDao.findOneByEmail(email);
        if (userByEmail != null) {
            return new ResponseResult(ResponseState.ERROR, "邮箱已注册");
        }
        // 第四步：检查邮箱验证码是否正确
        String emailVerifyCode = (String) redisUtil.get(Constants.User.KEY_EMAIL_CONTENT + email);
        if (TextUtils.isEmpty(emailVerifyCode)) {
            return new ResponseResult(ResponseState.ERROR, "邮箱验证码已过期");
        }
        if (!emailVerifyCode.equals(emailCode)) {
            return new ResponseResult(ResponseState.ERROR, "邮箱验证码输入错误");
        }
        // 第五步：检查验证码是否正确
        String checkVerifyCode = (String) redisUtil.get(Constants.User.KEY_CAPTCHA_CONTENT + checkKey);
        if (TextUtils.isEmpty(checkVerifyCode)) {
            return new ResponseResult(ResponseState.ERROR, "验证码已过期");
        }
        if (!checkVerifyCode.equals(checkCode)) {
            return new ResponseResult(ResponseState.ERROR, "验证码输入错误");
        } else {
            // 清除redis中的验证码信息
            redisUtil.del(Constants.User.KEY_CAPTCHA_CONTENT + checkKey);
        }
        // 达到注册条件
        // 第六步：对密码进行加密
        if (TextUtils.isEmpty(user.getPassWord())) {
            return new ResponseResult(ResponseState.ERROR, "密码不能为空");
        }
        user.setPassWord(cryptPasswordEncoder.encode(user.getPassWord()));
        // 第七步：补全数据
        // 注册ip，登录ip，角色，头像，创建时间，更新时间
        user.setId(String.valueOf(idWorker.nextId()));
        user.setRoles(Constants.User.ROLE_NORMAL);
        user.setAvatar(Constants.User.DEFAULT_AVATAR);
        user.setState(Constants.User.DEFAULT_STATE);
        String ipAddr = request.getRemoteAddr();
        user.setRegIp(ipAddr);
        user.setLoginIp(ipAddr);
        user.setCreateTime(new Date());
        user.setUpdateTime(new Date());
        // 第八步：保存数据
        userDao.save(user);
        // 删除redis中存储的验证码相关信息
        redisUtil.del(Constants.User.KEY_EMAIL_SEND_IP + (ipAddr.replaceAll(":", "_")));
        redisUtil.del(Constants.User.KEY_EMAIL_CONTENT + email);
        // 第九步：返回结果
        return new ResponseResult(ResponseState.SUCCESS, "注册成功");
    }

    @Override
    public User findOneByEmail(String email) {
        return userDao.findOneByEmail(email);
    }

    @Override
    public ResponseResult doLogin(String captcha, String captchaKey, User user, HttpServletRequest request, HttpServletResponse response) {
        // 1.判断验证码
        String captchaVerifyCode = (String) redisUtil.get(Constants.User.KEY_CAPTCHA_CONTENT + captchaKey);
        if (TextUtils.isEmpty(captchaVerifyCode)) {
            return new ResponseResult(ResponseState.ERROR, "验证码已过期");
        }
        if (!captchaVerifyCode.equals(captcha)) {
            return new ResponseResult(ResponseState.ERROR, "验证码输入错误");
        } else {
            redisUtil.del(Constants.User.KEY_CAPTCHA_CONTENT + captchaKey);
        }
        // 2.判断用户名/邮箱、密码是否为空
        String userName = user.getUserName();
        if (TextUtils.isEmpty(userName)) {
            return new ResponseResult(ResponseState.ERROR, "用户名不能为空");
        }
        String passWord = user.getPassWord();
        if (TextUtils.isEmpty(passWord)) {
            return new ResponseResult(ResponseState.ERROR, "密码不能为空");
        }
        // 3.判断为用户名还是邮箱，进行登录
        User userByUserNameOrEmail = null;
        if (TextUtils.isEmailAddress(userName)) {
            // 邮箱登录
            userByUserNameOrEmail = userDao.findOneByEmail(userName);
        } else {
            // 用户名登录
            userByUserNameOrEmail = userDao.findOneByuserName(userName);
        }
        if (userByUserNameOrEmail == null) {
            return new ResponseResult(ResponseState.ERROR, "用户名错误或用户不存在");
        }
        // 4.对比密码
        boolean matches = cryptPasswordEncoder.matches(user.getPassWord(), userByUserNameOrEmail.getPassWord());
        if (!matches) {
            return new ResponseResult(ResponseState.ERROR, "密码不正确");
        }
        // 5.判断用户状态
        if (!"1".equals(userByUserNameOrEmail.getState())) {
            return new ResponseResult(ResponseState.ERROR, "该账号已被冻结");
        } else {
            // 6.生成token
            // 生成token前清除redisToken与refreshToken的信息用户信息
            String md5Token = CookieUtils.getCookie(request, "blog_login_token");
            if (TextUtils.isEmpty(md5Token)) {
                // redis中自动删除
                // refreshToken删除
                refreshTokenDao.deleteOneByuserId(userByUserNameOrEmail.getId());
            } else {
                // redis与refreshToken同时删除
                redisUtil.del(Constants.User.KEY_TOKEN + md5Token);
                refreshTokenDao.deleteOneByuserId(userByUserNameOrEmail.getId());
            }
            createToken(response, userByUserNameOrEmail);
            // 事务前更新updateTime
            userByUserNameOrEmail.setUpdateTime(new Date());
            // 7.登录成功
            return new ResponseResult(ResponseState.LOGIN_SUCCESS);
        }
    }

    private String createToken(HttpServletResponse response, User userByUserName) {
        Map<String, Object> claims = ClaimsUtils.UserToClaims(userByUserName);
        String token = JwtUtil.createToken(claims);
        // md5加密token
        String md5Token = DigestUtils.md5DigestAsHex(token.getBytes());
        // 写入cookie，并保存到redis中2小时
        redisUtil.set(Constants.User.KEY_TOKEN + md5Token, token, 60 * 2);
        CookieUtils.setUpCookie(response, "blog_login_token", md5Token);
        // 生成refreshToken，保存一个月
        // refreshToken单位为毫秒
        String refreshTokenValue = JwtUtil.createRefreshToken(userByUserName.getId(), Constants.TimeValue.MONTH);
        // 保存到数据库
        RefreshToken refreshToken = new RefreshToken();
        refreshToken.setId(String.valueOf(idWorker.nextId()));
        refreshToken.setRefreshToken(refreshTokenValue);
        refreshToken.setTokenKey(md5Token);
        refreshToken.setUserId(userByUserName.getId());
        refreshToken.setCreateTime(new Date());
        refreshToken.setUpdateTime(new Date());
        refreshTokenDao.save(refreshToken);
        return md5Token;
    }

    @Override
    public User checkUser(HttpServletRequest request, HttpServletResponse response) {
        String md5Token = CookieUtils.getCookie(request, "blog_login_token");
        if (!TextUtils.isEmpty(md5Token)) {
            String claimsToken = (String) redisUtil.get(Constants.User.KEY_TOKEN + md5Token);
            RefreshToken oneBytokenKey = null;
            // redis未过期
            if (!TextUtils.isEmpty(claimsToken)) {
                try {
                    // jwt逆解析
                    Claims claims = JwtUtil.parseJWT(claimsToken);
                    User user = ClaimsUtils.ClaimsToUser(claims);
                    // 更新redis登录信息
                    redisUtil.set(Constants.User.KEY_TOKEN + md5Token, claimsToken, 60 * 2);
                    return user;
                } catch (Exception e) {
                    // redis中的claims过期
                    // 清除redis中的
                    redisUtil.del(md5Token);
                    // 数据库中查找
                    oneBytokenKey = refreshTokenDao.findOneBytokenKey(md5Token);
                    if (oneBytokenKey != null) {
                        try {
                            // jwt逆解析
                            // Claims claims = JwtUtil.parseJWT(oneBytokenKey.getRefreshToken());
                            // String userId = (String) claims.get("id");
                            String userId = oneBytokenKey.getUserId();
                            User userById = userDao.findOneById(userId);
                            // 删除原来的refreshToken
                            refreshTokenDao.deleteById(oneBytokenKey.getId());
                            // 重新生成token
                            String tokenKey = createToken(response, userById);
                            return userById;
                        } catch (Exception e2) {
                            // 数据库中refreshToken过期
                            // 清空数据库
                            refreshTokenDao.deleteById(oneBytokenKey.getId());
                            // 请登录
                            return null;
                        }
                    } else {
                        // 数据库中不存在
                        // 请登录
                        return null;
                    }
                }
            } else {
                // redis为空
                // 数据库中查找
                oneBytokenKey = refreshTokenDao.findOneBytokenKey(md5Token);
                if (oneBytokenKey != null) {
                    try {
                        // jwt逆解析
                        String userId = oneBytokenKey.getUserId();
                        User userById = userDao.findOneById(userId);
                        // 删除原来的refreshToken
                        refreshTokenDao.deleteById(oneBytokenKey.getId());
                        // 重新生成token

                        // 防止事务提交导致数据库中密码为空
                        // 不设置可能会导致密码泄露
                        // TODO:有待考虑，可以考虑使用GSON
                        // userById.setPassWord("");

                        String tokenKey = createToken(response, userById);
                        return userById;
                    } catch (Exception e2) {
                        // 数据库中refreshToken过期
                        // 清空数据库
                        refreshTokenDao.deleteOneById(oneBytokenKey.getId());
                        // 请登录
                        return null;
                    }
                } else {
                    // 数据库中不存在
                    // 请登录
                    return null;
                }
            }
        }
        return null;
    }

    @Override
    public ResponseResult getUserInfo(String userId) {
        User userById = userDao.findOneById(userId);
        if (userById == null) {
            return new ResponseResult(ResponseState.ERROR, "用户不存在");
        }
        String userJson = gson.toJson(userById);
        User newUser = gson.fromJson(userJson, User.class);
        newUser.setPassWord("");
        newUser.setEmail("");
        newUser.setRegIp("");
        newUser.setLoginIp("");
        return new ResponseResult(ResponseState.SUCCESS, "查询成功", newUser);
    }

    @Override
    public ResponseResult checkEmail(String email) {
        if (TextUtils.isEmpty(email)) {
            return new ResponseResult(ResponseState.ERROR, "邮箱地址不能为空");
        }
        // 检查邮箱地址是否正确
        if (!TextUtils.isEmailAddress(email)) {
            return new ResponseResult(ResponseState.ERROR, "邮箱地址输入有误");
        }
        User userByEmail = userDao.findOneByEmail(email);
        return userByEmail != null ? new ResponseResult(ResponseState.ERROR, "邮箱已注册") : new ResponseResult(ResponseState.SUCCESS, "邮箱未注册");
    }

    @Override
    public ResponseResult checkUserName(String userName) {
        if (TextUtils.isEmpty(userName)) {
            return new ResponseResult(ResponseState.ERROR, "用户名不能为空");
        }
        User userByUserName = userDao.findOneByuserName(userName);
        return userByUserName != null ? new ResponseResult(ResponseState.ERROR, "用户名已被使用") : new ResponseResult(ResponseState.SUCCESS, "用户名未使用");
    }

    @Override
    public ResponseResult updateUserInfo(HttpServletRequest request, HttpServletResponse response, String userId, User user) {
        User userFromToken = checkUser(request, response);
        if (userFromToken == null) {
            return new ResponseResult(ResponseState.NOT_LOGIN);
        }
        User userAccount = userDao.findOneById(userFromToken.getId());
        if (!userAccount.getId().equals(userId)) {
            return new ResponseResult(ResponseState.NO_PERMISSION);
        }
        // 修改头像
        if (!TextUtils.isEmpty(user.getAvatar())) {
            userAccount.setAvatar(user.getAvatar());
        }
        // 修改签名
        userAccount.setSign(user.getSign());
        // 修改用户名
        String userName = user.getUserName();
        if (!TextUtils.isEmpty(userName)) {
            User userByUserName = userDao.findOneByuserName(userName);
            if (userByUserName != null) {
                return new ResponseResult(ResponseState.ERROR, "用户名已被使用");
            }
            userAccount.setUserName(userName);
        }
        // 更新时间
        userAccount.setUpdateTime(new Date());
        userDao.save(userAccount);
        return new ResponseResult(ResponseState.SUCCESS, "修改成功");
    }

    @Override
    public ResponseResult deleteUserById(HttpServletRequest request, HttpServletResponse response, String userId) {
        // 删除用户
        int result = userDao.deleteUserById(userId);
        if (result > 0) {
            return new ResponseResult(ResponseState.SUCCESS, "删除成功");
        }
        return new ResponseResult(ResponseState.ERROR, "删除失败");
    }

    @Override
    public ResponseResult listUser(int page, int size, HttpServletRequest request, HttpServletResponse response) {
        // 获取用户列表
        // 分页查询
        if (page < Constants.PageValue.DEFAULT_PAGE) {
            page = Constants.PageValue.DEFAULT_PAGE;
        }
        if (size < Constants.PageValue.MIN_SIZE) {
            size = Constants.PageValue.MIN_SIZE;
        }
        // 分页
        // 根据日期排序
        Sort sort = Sort.by(Sort.Direction.DESC, "createTime");
        Pageable pageable = PageRequest.of(page - 1, size, sort);
        Page<User> users = userDao.listAllUserNoPassword(pageable);
        return new ResponseResult(ResponseState.SUCCESS, "获取用户列表成功", users);
    }

    @Override
    public ResponseResult findPassword(String verifyCode, User user, HttpServletRequest request) {
        // 密码非空
        String passWord = user.getPassWord();
        if (TextUtils.isEmpty(passWord)) {
            return new ResponseResult(ResponseState.ERROR, "密码不能为空");
        }
        // 检查邮箱
        String email = user.getEmail();
        if (TextUtils.isEmpty(email)) {
            return new ResponseResult(ResponseState.ERROR, "邮箱不能为空");
        }
        if (!TextUtils.isEmailAddress(email)) {
            return new ResponseResult(ResponseState.ERROR, "请输入正确的邮箱地址");
        }
        String emailVerifyCode = (String) redisUtil.get(Constants.User.KEY_EMAIL_CONTENT + email);
        if (!emailVerifyCode.equals(verifyCode)) {
            return new ResponseResult(ResponseState.ERROR, "你输入的验证码有误");
        }
        redisUtil.del(Constants.User.KEY_EMAIL_CONTENT + email);
        int result = userDao.updatePasswordByEmail(cryptPasswordEncoder.encode(passWord), email);
        if (result > 0) {
            // 删除redis中存储的验证码相关信息
            String ipAddr = request.getRemoteAddr();
            redisUtil.del(Constants.User.KEY_EMAIL_SEND_IP + (ipAddr.replaceAll(":", "_")));
            return new ResponseResult(ResponseState.SUCCESS, "修改成功");
        }
        return new ResponseResult(ResponseState.ERROR, "修改失败");
    }

    @Override
    public ResponseResult updateEmail(String email, String verifyCode, HttpServletRequest request, HttpServletResponse response) {
        // 确保登录了
        User user = checkUser(request, response);
        if (user == null) {
            return new ResponseResult(ResponseState.NOT_LOGIN);
        }
        // 对比验证码
        String emailVerifyCode = (String) redisUtil.get(Constants.User.KEY_EMAIL_CONTENT + email);
        if (!emailVerifyCode.equals(verifyCode)) {
            return new ResponseResult(ResponseState.ERROR, "你输入的验证码有误");
        }
        redisUtil.del(Constants.User.KEY_EMAIL_CONTENT + email);
        // 修改邮箱
        int result = userDao.updateEmail(email, new Date(), user.getId());
        if (result > 0) {
            // 删除redis中存储的验证码相关信息
            String ipAddr = request.getRemoteAddr();
            redisUtil.del(Constants.User.KEY_EMAIL_SEND_IP + (ipAddr.replaceAll(":", "_")));
            return new ResponseResult(ResponseState.SUCCESS, "修改成功");
        }
        return new ResponseResult(ResponseState.ERROR, "修改失败");
    }

    @Override
    public ResponseResult doLogout(HttpServletRequest request, HttpServletResponse response) {
        String tokenKey = CookieUtils.getCookie(request, "blog_login_token");
        if (TextUtils.isEmpty(tokenKey)) {
            return new ResponseResult(ResponseState.NOT_LOGIN);
        }
        // 删除redis中的token
        redisUtil.del(Constants.User.KEY_TOKEN + tokenKey);
        // 删除数据库中的refreshToken
        int result = refreshTokenDao.deleteOneBytokenKey(tokenKey);
        if (result <= 0) {
            return new ResponseResult(ResponseState.LOGOUT_ERROR);
        }
        // 删除cookie中的tokenKey
        CookieUtils.deleteCookie(response, "blog_login_token");
        return new ResponseResult(ResponseState.LOGOUT_SUCCESS);
    }
}

